﻿// Copyright 2004-2014 Castle Project - http://www.castleproject.org/
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
//     http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#region 导入名称空间

using System;
using System.Collections.Generic;
using Common.Logging;
using LightWork.DynamicProxy.Generators;
using LightWork.DynamicProxy.Internal;

#endregion

namespace LightWork.DynamicProxy
{
    /// <summary>
    /// Default implementation of <see cref="IProxyBuilder" /> interface producing in-memory proxy assemblies.
    /// </summary>
    public class DefaultProxyBuilder : IProxyBuilder
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="DefaultProxyBuilder" /> class with new <see cref="ModuleScope" />.
        /// </summary>
        public DefaultProxyBuilder()
            : this(new ModuleScope())
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="DefaultProxyBuilder" /> class.
        /// </summary>
        /// <param name="scope">The module scope for generated proxy types.</param>
        public DefaultProxyBuilder(ModuleScope scope)
        {
            ModuleScope = scope;
        }

        public ILog Logger { get; set; } = LogManager.GetLogger<DefaultProxyBuilder>();

        public ModuleScope ModuleScope { get; }

        public Type CreateClassProxyType(Type classToProxy, Type[] additionalInterfacesToProxy,
            ProxyGenerationOptions options)
        {
            AssertValidType(classToProxy);
            AssertValidTypes(additionalInterfacesToProxy);

            var generator = new ClassProxyGenerator(ModuleScope, classToProxy)
            {
                Logger = Logger
            };
            return generator.GenerateCode(additionalInterfacesToProxy, options);
        }

        public Type CreateClassProxyTypeWithTarget(Type classToProxy, Type[] additionalInterfacesToProxy,
            ProxyGenerationOptions options)
        {
            AssertValidType(classToProxy);
            AssertValidTypes(additionalInterfacesToProxy);
            var generator = new ClassProxyWithTargetGenerator(ModuleScope, classToProxy, additionalInterfacesToProxy,
                options)
            {
                Logger = Logger
            };
            return generator.GetGeneratedType();
        }

        public Type CreateInterfaceProxyTypeWithTarget(Type interfaceToProxy, Type[] additionalInterfacesToProxy,
            Type targetType,
            ProxyGenerationOptions options)
        {
            AssertValidType(interfaceToProxy);
            AssertValidTypes(additionalInterfacesToProxy);

            var generator = new InterfaceProxyWithTargetGenerator(ModuleScope, interfaceToProxy)
            {
                Logger = Logger
            };
            return generator.GenerateCode(targetType, additionalInterfacesToProxy, options);
        }

        public Type CreateInterfaceProxyTypeWithTargetInterface(Type interfaceToProxy,
            Type[] additionalInterfacesToProxy,
            ProxyGenerationOptions options)
        {
            AssertValidType(interfaceToProxy);
            AssertValidTypes(additionalInterfacesToProxy);

            var generator = new InterfaceProxyWithTargetInterfaceGenerator(ModuleScope, interfaceToProxy)
            {
                Logger = Logger
            };
            return generator.GenerateCode(interfaceToProxy, additionalInterfacesToProxy, options);
        }

        public Type CreateInterfaceProxyTypeWithoutTarget(Type interfaceToProxy, Type[] additionalInterfacesToProxy,
            ProxyGenerationOptions options)
        {
            AssertValidType(interfaceToProxy);
            AssertValidTypes(additionalInterfacesToProxy);

            var generator = new InterfaceProxyWithoutTargetGenerator(ModuleScope, interfaceToProxy)
            {
                Logger = Logger
            };
            return generator.GenerateCode(typeof(object), additionalInterfacesToProxy, options);
        }

        private void AssertValidType(Type target)
        {
            AssertValidTypeForTarget(target, target);
        }

        private void AssertValidTypeForTarget(Type type, Type target)
        {
            if (type.IsGenericTypeDefinition)
                throw new GeneratorException(
                    string.Format("Can not create proxy for type {0} because type {1} is an open generic type.",
                        target.GetBestName(), type.GetBestName()));
            if (IsPublic(type) == false && IsAccessible(type) == false)
                throw new GeneratorException(ExceptionMessageBuilder.CreateMessageForInaccessibleType(type, target));
            foreach (var typeArgument in type.GetGenericArguments())
                AssertValidTypeForTarget(typeArgument, target);
        }

        private void AssertValidTypes(IEnumerable<Type> targetTypes)
        {
            if (targetTypes != null)
                foreach (var t in targetTypes)
                    AssertValidType(t);
        }

        private bool IsAccessible(Type target)
        {
            return IsInternal(target) && target.Assembly.IsInternalToDynamicProxy();
        }

        private bool IsPublic(Type target)
        {
            return target.IsPublic || target.IsNestedPublic;
        }

        private static bool IsInternal(Type target)
        {
            var isTargetNested = target.IsNested;
            var isNestedAndInternal = isTargetNested && (target.IsNestedAssembly || target.IsNestedFamORAssem);
            var isInternalNotNested = target.IsVisible == false && isTargetNested == false;

            return isInternalNotNested || isNestedAndInternal;
        }
    }
}